昨天的結論是「平均而言 MoE 比較划算」
今天要處理的是那個「平均」兩個字
因為 Day 2 就已經告訴我們一件事:這份文件根本不是均質的。純文字頁覆蓋率 99.4%,圖例清單頁 76.2%,同一顆模型、同一個 prompt,中間差了 23 個百分點
那憑什麼用同一種跑法跑完全部?
我決定先講這件事,因為它是這篇最容易被誤讀的地方。
標題寫「50 tok/s 與 7 tok/s」。你第一眼會以為那是兩組實測數據對照。
不是。
我沒有跑過 31B Dense。這篇沒有任何新實驗。
今天全部的數字只有兩個來源:experiments/ 底下 Day 2 那批實測落檔,以及 Day 3、Day 5 已經寫過的換算。推導式我會全部寫出來,你可以自己驗算。
💡Tip: 為什麼不乾脆把標題改成「48.5 tok/s 與 7.5 tok/s」?因為取整的標題好讀,而且我覺得讓你看到取整的過程,比藏起來假裝精確誠實。這系列後面還會出現很多「約」跟「量級」,處理方式都一樣:取整可以,但取整的原始值跟換算式必須在文章裡找得到。
這五頁全部出自 step2_results.jsonl,是 Day 2 那次逐頁單請求跑出來的。條件一致:同一個 prompt、temperature=0、max_tokens=1500、圖片走 base64 data URI。
| 頁 | 類型 | prompt tok | completion tok | 端到端耗時 | tok/s | finish_reason |
|---|---|---|---|---|---|---|
| 1 | 純文字 | 298 | 141 | 3.401s | 41.46 | stop |
| 9 | 混排 | 298 | 387 | 8.114s | 47.70 | stop |
| 11 | 表格 | 298 | 737 | 15.182s | 48.54 | stop |
| 12 | 表格 | 298 | 459 | 9.521s | 48.21 | stop |
| 14 | 混排 | 298 | 358 | 7.776s | 46.04 | stop |
現在請你先看 tok/s 那一欄,然後告訴我一件事:
哪一頁最慢?
答案是純文字頁。41.46 tok/s,全場最低。
而表格頁是 48.54 跟 48.21,全場最高。
這跟直覺完全相反。你會預期表格比較難、比較慢,結果它的 tok/s 反而最快。
原因 Day 3 已經解釋過了:短輸出的 tok/s 會被 prefill 跟網路往返拖低。
第 1 頁只吐了 141 個 token,那 3.4 秒裡有相當一部分不是 decode,是「處理圖片 + 一來一回」的固定成本。這筆固定成本攤到 141 個 token 上很貴;攤到 737 個 token 上就幾乎看不見了。
所以 Day 3 的收斂值 48.5 tok/s 才是這顆模型真正的 decode 速度。表格頁那兩筆(48.54、48.21)剛好就是這個值。
這帶出今天的第一個結論,而且它很重要:
decode 速度跟頁面類型無關。模型吐每一個 token 的速度都一樣快,不管那個 token 是中文字、是數字、還是一根
|。
那表格區到底慢在哪裡?
慢在它要吐的 token 多太多了。
這一節的數字全部來自 analysis/metrics_summary.csv,欄位是腳本自己算的,不是我手抄的。
gt_raw_len 是 PyMuPDF 抽出來的原生文字層長度(也就是「這頁到底有多少字」),hyp_raw_len 是模型實際吐出來的字元數:
| 頁 | 類型 | 原文字元 | 模型輸出字元 | 膨脹倍率 |
|---|---|---|---|---|
| 1 | 純文字 | 179 | 198 | 1.11x |
| 9 | 混排 | 409 | 765 | 1.87x |
| 11 | 表格 | 523 | 1042 | 1.99x |
| 12 | 表格 | 324 | 660 | 2.04x |
| 14 | 混排 | 256 | 519 | 2.03x |
純文字頁 1.11 倍,表格頁 約 2 倍。
那多出來的一倍是什麼?
是 Markdown 表格的骨架。 每一格都要包 |、每個表頭底下要有一整列 ---、每一列結尾要換行。
Day 2 我就注意到這件事了(「第 11 頁文字層只有 523 字,模型卻吐了 737 個 token」),但當時只當成一個觀察。今天我要把它變成一個可以算的成本。
compare_ocr.py 在比對之前會做一次正規化:NFKC 全半形統一、剝掉 Markdown 的 | --- ** 等符號、去掉項目符號、去掉所有空白。
正規化前後的差距,就是「格式成本」:
| 頁 | 類型 | 輸出原始字元 | 正規化後 | 內容佔比 |
|---|---|---|---|---|
| 1 | 純文字 | 198 | 182 | 0.919 |
| 9 | 混排 | 765 | 286 | 0.374 |
| 11 | 表格 | 1042 | 440 | 0.422 |
| 12 | 表格 | 660 | 305 | 0.462 |
| 14 | 混排 | 519 | 235 | 0.453 |
純文字頁:模型吐出來的東西,92% 是內容。
表格頁:只有 42%。
換句話說,跑表格區的時候,這顆模型有將近六成的時間在畫框線。
而它畫框線的速度跟它辨識文字的速度一模一樣——都是 48.5 tok/s。GPU 不知道哪個比較重要。
💡Tip: 這件事有一個很實際的推論:如果你不需要 Markdown 表格,就不要叫它輸出 Markdown 表格。 換成 JSON、TSV、或是「一格一行」的極簡格式,格式 token 的佔比會差很多。我 Day 2 的 prompt 寫的是「保留表格結構」,那是刻意不做 prompt engineering 的 baseline;真的要上線的時候,輸出格式本身就是一個效能參數。這條會一路影響到 Day 22 的結構化輸出驗證——格式越簡單,Pydantic 越好驗。
現在把「原始 decode 速度」跟「內容佔比」乘在一起,得到我今天真正想量的東西:
有效產出 tok/s = 原始 decode tok/s × 內容佔比
| 頁 | 類型 | 原始 tok/s | 內容佔比 | 有效產出 tok/s |
|---|---|---|---|---|
| 1 | 純文字 | 41.46 | 0.919 | 38.1 |
| 9 | 混排 | 47.70 | 0.374 | 17.8 |
| 11 | 表格 | 48.54 | 0.422 | 20.5 |
| 12 | 表格 | 48.21 | 0.462 | 22.3 |
| 14 | 混排 | 46.04 | 0.453 | 20.8 |
原始速度幾乎一樣(41–49),有效產出差了將近一倍(17.8–38.1)。
再換一個角度看同一件事——用牆鐘時間除以原文字元數,得到「讀完一個字要多久」:
| 頁 | 類型 | 耗時 | 原文字元 | 每字元秒數 |
|---|---|---|---|---|
| 1 | 純文字 | 3.401s | 179 | 0.0190 |
| 9 | 混排 | 8.114s | 409 | 0.0198 |
| 11 | 表格 | 15.182s | 523 | 0.0290 |
| 12 | 表格 | 9.521s | 324 | 0.0294 |
| 14 | 混排 | 7.776s | 256 | 0.0304 |
純文字區約 0.019 秒/字,表格與混排區約 0.029–0.030 秒/字。
表格區每讀一個字,貴大約 1.5 倍。
注意這兩個角度算出來的倍率不一樣(一個接近 2 倍,一個 1.5 倍),因為分母定義不同:前者除的是模型的輸出,後者除的是原文的長度。我把兩個都列出來,是因為它們回答不同的問題——你要算 GPU 帳單看前者,你要算「一份文件要跑多久」看後者。
速度講完了,換另一半。同一張 CSV,看評分欄位:
| 頁 | 類型 | Levenshtein | CER | 覆蓋率 | diff 塊數 |
|---|---|---|---|---|---|
| 1 | 純文字 | 15 | 0.0888 | 0.9941 | 3 |
| 11 | 表格 | 55 | 0.1341 | 0.9878 | 7 |
| 12 | 表格 | 94 | 0.3643 | 0.9806 | 9 |
| 14 | 混排 | 165 | 0.7933 | 0.8894 | 7 |
| 9 | 混排 | 161 | 0.5111 | 0.7619 | 34 |
(覆蓋率那四個 98%+ / 76.2% 的數字就是 Day 2 那張表,Day 5 也把它列進「Phase 1 的實驗資產」當成那把尺。第 14 頁的 88.9% 是這次一併算出來但 Day 2 沒展開講的,今天補上。)
三件事值得講:
① 純文字區在速度跟品質上同時領先
0.9941 的覆蓋率、3 個 diff 塊、38.1 的有效產出。它是這份文件裡最便宜也最可靠的部分。
② 有框線的表格,品質其實不差
0.9878 跟 0.9806,跟純文字頁只差 1 個百分點左右。Day 2 講過原因:框線給了模型明確的結構線索。
所以表格區的問題不是準確率,是成本。這個區分很關鍵,因為它決定了你該用什麼手段去解——準確率問題要換模型或加驗證,成本問題要換格式或換路由。
③ 混排頁才是真正的壞區
第 9 頁:覆蓋率 0.7619,34 個 diff 塊——是純文字頁的 11 倍。
第 14 頁:覆蓋率 0.8894,有效產出 20.8。
混排區是「又慢又不準」那一格。 它同時吃掉表格的格式成本,又沒有框線給的結構線索。
你看第 14 頁:CER 高達 0.7933,覆蓋率卻有 0.8894。
這兩個數字看起來在講相反的話。
原因是 CER 對「重排」極度敏感。compare_ocr.py 算的是正規化後的字元級 Levenshtein 距離除以原文長度——只要模型把內容的順序換了(例如文字層是拉平的、模型輸出的是表格),即使一個字都沒錯,CER 也會被灌爆。
覆蓋率算的是多重集合的交集,它不管順序,只問「該有的字有沒有出現」。
所以 Day 2 選了覆蓋率當主指標。
但覆蓋率也有它的盲點:它看不出順序,也就意味著它抓不到 Day 2 那條「雙表格被揉成一張」的錯誤——每個數字都在,只是擺錯格子,覆蓋率會給你滿分。
💡Tip: 這就是為什麼我把 CER、覆蓋率、diff 塊數三個一起列在同一張表。任何單一指標都有它系統性看不到的失敗模式。 覆蓋率看不到順序、CER 看不到「錯得有沒有道理」、diff 塊數看不到嚴重程度(一個負號跟一整列漏掉,在塊數上都算 1)。這條原則 Day 22 會變成「四重驗證」的骨架——可信任是疊出來的,不是宣稱出來的。
現在把所有零件裝起來。
我要算的是:如果我真的決定「純文字區用快的、表格區用準的」,那個「準的」要付多少代價?
原始 decode 速度 = 48.5 tok/s ← Day 3 長輸出收斂實測值
內容佔比 = 182 / 198 = 0.919 ← metrics_summary.csv, page_01
有效產出 = 48.5 × 0.919 ≈ 44.6 tok/s
(注意我用的是 48.5 而不是 page_01 自己那次的 41.46。理由前面講過:141 個 token 的短輸出還沒把 prefill 攤薄,41.46 低估了這顆模型的真實 decode 速度。)
先拿 Day 5 已經算過的天花板:
31B Dense 權重 = 30.7B × 0.5 byte ≈ 15.35 GB ← NVFP4
理論上限 = 273 GB/s ÷ 15.35 GB ≈ 17.8 tok/s
再乘上表格區的內容佔比:
內容佔比 = 440 / 1042 = 0.422 ← metrics_summary.csv, page_11
有效產出 = 17.8 × 0.422 ≈ 7.5 tok/s
7.5。取整描述就是標題那個 7。
我把它們一條一條攤開,因為這裡有兩層推估疊在一起:
| 假設 | 屬性 |
|---|---|
| 31B Dense 的參數量是 30.7B | ⚠️ 沿用 Day 5 的換算基數,一手規格出處未查證 |
| 它會用 NVFP4,每參數 0.5 byte | 推估(跟我現在這顆一致) |
| 17.8 tok/s 是天花板,不是預期值 | 理論推算(Day 3 的式子) |
| Dense 模型的輸出格式習慣跟 MoE 版一樣,所以內容佔比也是 0.422 | ⚠️ 推估,我沒測過 |
| 我實際跑過 31B Dense | ❌ 沒有。這篇沒有新實驗 |
倒數第二條是最弱的一環:不同模型的囉唆程度本來就不一樣,內容佔比直接沿用是一個相當粗的假設。
而且第三條還有一個方向性的問題——17.8 是天花板。我這顆 MoE 的實測只有天花板的 34%。如果 Dense 也有類似比例的實作損耗,那 7.5 這個數字還是樂觀的。
標題刻意混用了兩種算法(左邊用原始速度、右邊用有效產出)。我把三種比法全部列出來,你要用哪一把自己挑:
| 比法 | 純文字區 | 表格區 | 倍差 |
|---|---|---|---|
| 同一把尺:原始 decode tok/s | 48.5(實測) | 17.8(理論天花板) | 2.7x |
| 同一把尺:有效產出 tok/s | 44.6(實測 × 實測換算) | 7.5(理論推算) | 5.9x |
| 標題那把尺(混用) | 50(≈48.5,原始實測) | 7(≈7.5,有效產出推算) | 7.1x |
為什麼我還是用了混用的那一把當標題?
因為它反映了決策現場的真實感受:你在監控面板上看到的是 50(原始 tok/s,這是所有推論框架預設給你的數字),而你真正拿到手的、可以用的東西是 7。
你看得到的跟你付出的,不是同一個數字。
但這個「反映感受」只能拿來當標題,不能拿來當論證。所以完整的三行都在上面那張表裡。
💡Tip: 如果你只帶走一條方法論,我希望是這條:任何「A 比 B 快 N 倍」的句子,先問分子分母是不是同一個定義。 我上面同一組數據可以算出 2.7 倍、5.9 倍、7.1 倍三個答案,全部都不算說謊,差別只在尺。這也是為什麼我每次都把式子寫出來——結論可以爭論,式子不行。
tok/s 這個單位對人類沒有意義,所以最後一定要換成「一件事要幾分鐘」。
Day 2 給過一個粗估:「每頁 3–15 秒,40 頁單線跑完 5–10 分鐘」。那是拿單頁耗時的上下界直接外推,抓的是包絡。
今天有比較好的分母了。page_char_counts.txt 記了這份 PDF 全部 40 頁的文字層字數:
| 項目 | 值 |
|---|---|
| 總頁數 | 40 |
| 有文字的頁數 | 38 |
| 文字層總字元數 | 8,064 |
| 平均每頁 | 202 |
| 我抽樣那 5 頁的平均 | 338 |
第一件要注意的事:我抽的那 5 頁比全份平均長了 67%。
所以直接把 5 頁的平均耗時(8.80 秒)乘以 40 會高估——那樣算出來是 5.9 分鐘,而它高估的原因純粹是抽樣偏差,不是模型變慢了。
用前面那個「每字元秒數」重算,得到一個包絡:
| 假設 | 每字元秒數 | 8,064 字元 → 總時間 |
|---|---|---|
| 全部當純文字區跑 | 0.0190 | 約 2.6 分鐘 |
| 全部當表格區跑 | 0.0290 | 約 3.9 分鐘 |
| 全部當混排區跑(最壞) | 0.0304 | 約 4.1 分鐘 |
2.6 到 4.1 分鐘,落在 Day 2 那個粗估的下緣。兩個數字不衝突——Day 2 是拿單頁耗時的上界外推,今天是拿字元數加權,後者的分母比較誠實。
💡Tip: 這裡藏著一個做效能評估很常見的坑:我抽的樣本本來就偏長。 因為我當初是刻意挑「有代表性的版面」——純文字、表格、混排——而有內容的頁面天生就比較長。用這種樣本的平均去乘總頁數,等於把偏差乘了 40 倍。抽樣要有代表性,外推要用總量。 這兩件事要分開做,Day 27 講 2–5% 抽樣稽核的時候會再仔細處理一次。
(再標一次:我沒有幫 40 頁全部標版面類型,只標了 5 頁。所以上面是兩個端點的包絡,不是真實混合比例下的估計值。真實值會落在區間裡的某處,但我不知道是哪裡。)
順手把昨天那個假設也算完。31B Dense 相對 26B MoE 的速度比是 17.8 ÷ 48.5 ≈ 0.367,也就是時間變成 2.72 倍。
假設表格區佔了全份一半的字元(這個比例是我瞎抓的,純粹為了示範算法):
原本:3.9 分鐘
換後:3.9 × 0.5 + 3.9 × 0.5 × 2.72 ≈ 7.3 分鐘
從 3.9 分鐘變 7.3 分鐘,接近翻倍。
換來的是什麼?表格區覆蓋率從 98% 往上——而且我不知道往上多少,因為我沒跑過。
這就是昨天那句話的數字版本:我為了一個未知的改善,先付出一個確定的成本。
(這一段是推算疊在推算上:17.8 是理論天花板、2.72 是由它導出的比值、50% 是我瞎抓的分配。我把它寫出來是為了示範算法,不要把 7.3 分鐘當成一個預測值。)
前面算的都還只是「一次就成功」的成本。
實務上結構化輸出會失敗,失敗就要重試,重試就是再付一次全額。
但我必須誠實:重試率我沒有量。 experiments/ 底下五頁全部是 finish_reason: stop、http_status: 200,沒有任何一次失敗需要重試。單次跑五頁,本來也量不出重試率。
我有的是**「為什麼表格區會需要重試」的證據**——來自 step3_results.jsonl 那個三頁合併的壓力測試:
| 測試 | 輸出字元 | completion tok | 耗時 | tok/s | 覆蓋率 | diff 塊數 |
|---|---|---|---|---|---|---|
| a6_combined_3pages | 1979 | 1464 | 30.148s | 48.56 | 0.9124 | 35 |
對照三張表單獨跑的時候:0.9878、0.9806,diff 塊數 7 跟 9。
合併之後覆蓋率掉到 0.9124,diff 塊數 35。
而 finish_reason 還是 stop,tok/s 還是漂亮的 48.56。
Day 2 已經逐條講過這次漏了什麼(股東權益總計、流動比率、營業外收入及支出整列不見,還有那條最危險的負號遺失)。今天我要補的是它對成本的意義:
一次沒做對的表格區,你要付的不是 1 次的錢,是 N 次的錢,而且系統不會告訴你 N 是多少。
因為它沒有報錯。你要自己去比對才知道要重試。而比對本身也是成本——compare_ocr.py 那套 Levenshtein 是 O(n×m) 的,跑在 CPU 上,頁面越長越貴。
(另外標一下:那張表的 CER 我刻意沒列。因為 compare_ocr.py 的註解寫得很清楚——模型輸出的三頁順序是 12、13、11,跟 ground truth 串接的順序不同,所以那個 0.9589 幾乎全是重排造成的,拿來比對沒有意義。這就是前面講的 CER 盲點的實例。)
所以完整的成本結構應該長這樣:
表格區的真實成本 = (decode 時間 + 格式 token 的時間) × 重試次數 + 驗證時間
↑ 已量 0.422 ↑ 未量 ↑ 未量
三項裡我只量到第一項。 後面兩項要等 Day 22 的驗證機制跟 Day 25 的 flywheel 跑起來才會有數字。
我先把式子寫在這裡,之後回來填。
這節是給想自己跑一次的人。整條鏈路是三支腳本,全部在 experiments/ 底下:
# ① 渲染頁面 + 抽 ground truth(PyMuPDF,不碰 GPU)
python step1_render_pages.py
# 產出:pages/page_NN_<label>.png、pages/page_NN_<label>.gt.txt、pages_manifest.json
# ② 逐頁單請求 OCR,落檔原始回應
python step2_ocr_baseline.py
# 產出:raw_responses/*.response.json、*.model_output.txt、step2_results.jsonl
# ③ 正規化 + 逐字 diff + 指標彙總(純本機文字處理,不呼叫任何 API)
python analysis/compare_ocr.py
# 產出:analysis/diffs/*.diff.txt、analysis/metrics_summary.csv
| 要記的欄位 | 為什麼 |
|---|---|
elapsed_sec(端到端牆鐘) |
tok/s 的分母。不要只信 usage 裡的數字 |
prompt_tokens / completion_tokens |
分子,以及輸出膨脹的證據 |
finish_reason |
排除「被 max_tokens 截斷」這個假原因 |
gt_raw_len / hyp_raw_len |
算膨脹倍率 |
gt_norm_len / hyp_norm_len |
算內容佔比 |
| CER / 覆蓋率 / diff 塊數 | 三個一起看,單看任何一個都會被騙 |
step2_results.jsonl 有速度、metrics_summary.csv 有長度,今天那張有效產出表就是把兩邊 join 起來。這段是純讀檔計算,不碰網路:
import csv, json
speed = {}
with open("step2_results.jsonl", encoding="utf-8") as f:
for line in f:
d = json.loads(line)
speed["page_%02d_%s" % (d["page_idx"], d["label"])] = d
with open("analysis/metrics_summary.csv", encoding="utf-8") as f:
for r in csv.DictReader(f):
d = speed.get(r["name"])
if not d: # 壓力測試那列不在 step2 裡,跳過
continue
gt_raw, hyp_raw = int(r["gt_raw_len"]), int(r["hyp_raw_len"])
content_ratio = int(r["hyp_norm_len"]) / hyp_raw # 內容佔比
print(
r["name"],
"膨脹 %.2fx" % (hyp_raw / gt_raw),
"內容佔比 %.3f" % content_ratio,
"有效產出 %.1f tok/s" % (d["completion_tok_per_sec"] * content_ratio),
"每字元 %.4f s" % (d["elapsed_sec"] / gt_raw),
)
十幾行,沒有任何一個常數是我手打進去的——這點很重要。文章裡的數字只要有一個是手抄的,它遲早會跟落檔對不上。
① tok/s 在短輸出上一定偏低
第 1 頁那個 41.46 就是例子。只在輸出長度相近的樣本之間比較 tok/s,或者乾脆只用長輸出的收斂值。
② 正規化規則決定你的 CER,所以規則必須公開
compare_ocr.py 的 normalize() 做了哪些事,我在腳本的 docstring 裡逐條寫出來了——去空白、剝 Markdown 符號、NFKC、去項目符號。換一套規則,同一份輸出可以算出完全不同的 CER。 只給指標不給正規化規則,等於沒給。
③ ground truth 本身有瑕疵,而且你要知道是哪些
PyMuPDF 抽出來的文字層有 PUA 區塊的怪字元(Wingdings 風格的項目符號),閱讀順序也常常跟視覺版面不一致。compare_ocr.py 的註解裡特別寫了「這些怪字是 ground truth 的抽取瑕疵,不在 normalize() 裡做替換,以免掩蓋掉需要分辨 gt 缺陷的資訊」。
這是刻意的選擇:寧可讓指標難看一點,也不要把 ground truth 的問題偷偷洗掉。
④ 比對順序不同的輸出時,CER 直接放棄
見前面那個三頁合併的例子。
⑤ 每一次都落檔
Day 3 那條血淚:只跑在終端裡沒存檔的東西,事後等於沒跑過。這三支腳本全部都寫檔案,step2 連原始 HTTP 回應 JSON 都整份存下來。成本幾乎是零。
💡Tip: 第 ③ 點我想多講一句,因為它是自動化評測最容易自欺的地方。當你發現指標難看的時候,最誘人的修法是去改正規化規則。 改著改著,你的評測就變成「一套剛好讓我的模型看起來很好的規則」。我的做法是:正規化規則只在能舉出「這是格式差異、不是內容差異」的具體例子時才改,而且改完要重跑全部樣本,不能只看有沒有救到那一頁。
把今天的數據收斂成一張決策表。這是明天之後那些架構設計的直接源頭:
| 區塊類型 | 有效產出 | 覆蓋率 | 該用什麼 | 理由 |
|---|---|---|---|---|
| 純文字區 | 38.1(實測) | 0.9941 | 26B MoE,直接跑 | 又快又準,沒有任何理由加東西 |
| 有框線表格區 | 20.5–22.3(實測) | 0.98+ | 26B MoE + 輸出格式優化 | 問題是成本不是準確率,換格式比換模型划算 |
| 混排/清單區 | 17.8–20.8(實測) | 0.7619–0.8894 | 需要額外手段 | 又慢又不準,這裡才值得花錢 |
| (假想)表格區換 Dense | 7.5(理論推算) | 未知 | 暫不採用 | 付出確定的成本,換未知的改善 |
看最後兩行的對比。
昨天我說「31B Dense 沒被淘汰,它被降級成候選」。今天的數據給了它一個更明確的位置:
Dense 如果要上場,不該上在表格區。
因為表格區的覆蓋率已經 98% 以上了,換一顆慢 2.7 倍的模型去救那 2%,投資報酬率很差。
真正需要救的是混排區的 76.2%。 但那裡的問題我在 Day 2 就分析過——不是「認不認得字」的問題,是鬆散排列沒有結構線索的問題。換一顆更大的模型,不會憑空長出框線。
所以我的判斷是:
混排區需要的不是更大的模型,是先幫它「長出結構」。
也就是先做版面偵測、把區塊切開,再丟給模型。這正好是 Day 5 那張 Phase 1 收斂表裡唯一一條標 ✅ 的失敗模式(⑤ 雙表格被揉成一張 → 先做版面偵測,切開再丟 → 成本很低)。
而這件事,就是 Day 18 要組的 Region Routing。
今天的量測沒有把 Dense 送上場,反而把它送回候選席更後面一點——同時把「版面偵測」推到了第一順位。 這是我做這組量測之前沒有預期到的結論。
照慣例,把空白也列出來:
| 沒做的事 | 為什麼重要 |
|---|---|
| 沒有實際跑 31B Dense | 7.5 純屬推算,整個右半邊的結論都建立在推算上 |
| 沒有量重試率 | 成本式子裡最大的一個未知數 |
| 沒有測不同輸出格式(JSON / TSV)的內容佔比 | 「換格式比換模型划算」這句話目前也只是推論 |
| 樣本只有 5 頁、1 份文件 | 統計把握度低,這些是量級不是精確值 |
| 沒有 BF16 基線 | Day 5 講過的已知盲點——量化打壞了多少,我分不出來 |
5 頁的樣本量,我不會宣稱任何通則。 今天所有的數字都是「在這份法說會簡報上、在這台 GB10 上、用這顆 NVFP4 量化的 26B MoE」成立的。
今天的結論:
不均質的文件,不該用均質的跑法。而要知道哪裡不均質,你得先把尺拿出來量。
那量完之後呢?
你會發現一個更根本的問題:上面每一個「覆蓋率」數字,都是我事後拿 ground truth 比對算出來的。
但在真正的產線上,你沒有 ground truth。那份 PDF 如果是掃描檔,連文字層都沒有。
所以問題變成:當下、線上、沒有答案卷的時候,系統怎麼知道自己這一頁跑砸了?
最直覺的想法是「讓模型自己檢查一遍」。
明天我會告訴你這條路為什麼走不通——Day 5 講同源盲點的時候已經預告過了——以及我為什麼要另外請一顆 gpt-oss:20b 進來當 Verifier。
為什麼不能自己驗證自己? 明天見 👋